local utils = require('gray_utils')
local gcf = require('gray_conf')
local engine_creator = require('engine_conf_creator')
local nginx_parser = require('nginx_conf_parser')
local nginx_operator = require('nginx_operator') 

--[[ it's new code ]]

local pre_newindex = getmetatable(_G).__newindex
getmetatable(_G).__newindex = function (table, key, val)
    error('Attempt to write to undeclared variable "' .. key .. '": '
          .. debug.traceback())
end

-- admin conf
local ngx_bin_name = gcf.ngx_binary_name
local ngx_pid_name = gcf.ngx_pid_name
local ngx_conf_name = gcf.ngx_conf_name
local ngx_conf_test_name = gcf.ngx_conf_test_name
local ngx_conf_prefix = gcf.ngx_conf_prefix

local get_server_name = gcf.get_server_name

local gray_error_pathname = gcf.gray_error_pathname

-- engine conf
local engine_conf_basename = gcf.engine_conf_basename

-- nginx conf parser
local ngx_conf_insert_head = nginx_parser.ngx_conf_insert_head
local ngx_conf_insert_tail = nginx_parser.ngx_conf_insert_tail

-- utils
local ecode_success = utils.ecode_success
local ecode_again = utils.ecode_againe
local ecode_warn = utils.ecode_warn
local ecode_fatal_error = utils.ecode_fatal_error
local ecode_env_exception = utils.ecode_env_exception
local ecode_retry_after_modify = utils.ecode_retry_after_modify
local ecode_retry_later = utils.ecode_retry_later

local ngx_conf_basename = utils.basename(ngx_conf_name)

local print_and_log = utils.print_and_log
local icheck = utils.icheck

local arg_dowhat = ngx.var.arg_dowhat
local arg_status = ngx.var.arg_status
local gray_server_var_name = "$" .. gcf.gray_server_var_name
local gray_uri_var_name = "$" .. gcf.gray_uri_var_name
local gray_proxy_pass_value = string.format("http://%s%s$is_args$args",
                                            gray_server_var_name, gray_uri_var_name)

local phase_string = {"before write file to test", "write file to test",
                      "test new conf", "activate new conf", "before reload",
                      "after reload", "before recover", "after recover"}

local BEFORE_WRITE_FILE_TO_TEST = 1
local WRITE_FILE_TO_TEST = 2
local TEST_NEW_CONF = 3
local ACTIVATE_NEW_CONF = 4
local BEFORE_RELOAD_NEW_CONF = 5
local AFTER_RELOAD_NEW_CONF = 6
local BEFORE_RECOVER = 7
local AFTER_RECOVER = 8
local RESTORING = 0
local RESTORED = 1
local gray_admin_status = {}
gray_admin_status.phase = BEFORE_WRITE_FILE_TO_TEST


-- globals
local global_sid
local global_success_files
local ngx_pid

local cell = false
local cell_local_ups = "local"
local cell_app_var = "$app"

local function check_cell_conf(cf, jservers, sid)
    -- TODO check cell status (before gray, after gray)
    local cell_num = 0 
    local gray_num = 0
    local ngx_conf_servers = cf.ngx_conf_servers
    for _, srv in ipairs(ngx_conf_servers) do
        if srv.includes then
            for _, inc in ipairs(srv.includes) do
                if inc.PARSE_CONF.file_name == gcf.cell_server_conf or 
                   utils.basename(inc.PARSE_CONF.file_name) == gcf.cell_server_conf then
                    srv.CELL = true
                    break
                end
            end
        end
        if srv.sets then
            for _, set in ipairs(srv.sets) do
                if set.PARSE_CONF.var_name == cell_app_var then 
                    if string.match(set.PARSE_CONF.value, '%b""') then
                        srv.APP = string.sub(set.PARSE_CONF.value, 2, -2)
                    elseif string.match(set.PARSE_CONF.value, "%b''") then
                        srv.APP = string.sub(set.PARSE_CONF.value, 2, -2)
                    else
                        srv.APP = set.PARSE_CONF.value
                    end
                end
            end
        end
    end
    if jservers then
        for name in pairs(jservers.names) do
            local srv = cf.ngx_conf_server_name_map[name] 
            if srv == nil then
                return ecode_retry_after_modify, "server "..name.." not defined in configure"
            end
            if srv.CELL == true then
                cell_num = cell_num + 1
                jservers.conf.APP = srv.APP
            end
        end
        gray_num = jservers.num
    else
        local ngx_conf_servers = cf.ngx_conf_servers
        for _, srv in ipairs(ngx_conf_servers) do
            if srv.includes then
                for _, inc in ipairs(srv.includes) do
                    local sid_a = string.match(inc.PARSE_CONF.file_name,
                                     "gray_(%d+)/gray_srv_inc%.conf$")
                    if sid_a and tonumber(sid_a) == tonumber(sid) then
                        gray_num = gray_num + 1
                        if srv.CELL then cell_num = cell_num + 1 end
                        break
                    end
                end
            end
        end
                
    end
    if cell_num == gray_num then
        cell = true
    else
        if cell_num > 0 then
            return ecode_retry_after_modify, "some server is in cell, and some is not"
        end
    end
    if cell then
        gray_server_var_name = "$" .. gcf.cell_upstream_variable
        gray_proxy_pass_value = "http://" .. gray_server_var_name
    end
    -- TODO check $diamond_gray_servers ~= "" 
    return ecode_success
end


local function get_original_file_map(sid)
    local f, m = io.open(gcf.get_original_file_map_fullname(sid), "r")
    if not f then return nil, m end
    local original_file_map = {}
    for line in f:lines() do
        local cur_file, ori_file = string.match(line, "(%S+)[ ]+(%S+)")
        if cur_file and ori_file then
            original_file_map[cur_file] = ori_file
        else
            return nil, "bad original file map"
        end
    end
    f:close()
    return true, original_file_map
end


local function save_error_files(sid, success_created_files)
    local target_path = gray_error_pathname..arg_dowhat..sid..'_'..os.time()..'/'
    local rt, msg = utils.mkdir(target_path)
    if not rt then return nil, msg end
    if arg_dowhat == 'init' or arg_dowhat == 'test' then
        utils.copy(ngx_conf_test_name,
                   target_path..utils.basename(ngx_conf_test_name))
        utils.copy_dir(gcf.get_gray_sid_path(sid), target_path)
    end
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        utils.copy(ngx_conf_name, target_path..utils.basename(ngx_conf_name))
        -- TODO uninit vars
        utils.copy(gcf.get_engine_conf_fullname(sid),
                   target_path .. engine_conf_basename)
    end
    if arg_dowhat == 'clear' then
        if success_created_files then
            for fullname, _ in pairs(success_created_files) do
                utils.copy(fullname, target_path..utils.basename(fullname))
            end
        end
    end
    return true
end


local function update_server_locations(v, query)
    if cell then return true, 0 end
    local num = 0
    local function get_original_backend_in(sets, delete)
        if sets == nil then return nil end
        local count = 0
        local backend
        for _, set in ipairs(sets) do
           if set.PARSE_CONF.var_name == gray_server_var_name then 
               -- if found then delete it
               if delete then set.PARENT[set.INDEX].DELETE = 1 end
               backend = set.PARSE_CONF.value
               count = count + 1
           end
           if set.PARSE_CONF.var_name == gray_uri_var_name then 
               -- if found then delete it
               if delete then set.PARENT[set.INDEX].DELETE = 1 end
               count = count + 1
           end
           if count == 2 then return backend end
        end
        return nil
    end
    local function update_location(v)
        if v.proxy_pass then
            num = num + 1
            if not query then
                if arg_dowhat == 'init' then
                    if v.proxy_pass.ARGS[2] ~= gray_proxy_pass_value then
                        -- insert set directive
                        ngx_conf_insert_head(v, {ARGS = {"set",
                                                  gray_server_var_name,
                                                  v.proxy_pass.PARSE_CONF.host
                                                 }})

                        -- insert set $gray_uri $uri;
                        ngx_conf_insert_tail(v, {ARGS={'set', gray_uri_var_name,
                                             "$uri"}})
                        -- insert access_by_lua_file
                        ngx_conf_insert_head(v, {ARGS={
                                                'access_by_lua_file',
                                                 gcf.engine_script_fullname}})
                        v[v.proxy_pass.INDEX].ARGS[2] = gray_proxy_pass_value
                    end
                end
                if arg_dowhat == 'clear' then
                    -- recover original proxy_pass
                    if v.proxy_pass.ARGS[2] == gray_proxy_pass_value then
                        local backend = get_original_backend_in(v.sets, true)
                        if backend == nil then
                            return nil, "cann't find original backend, in location "..
                                   v.PARSE_CONF.name
                        end
                        v[v.proxy_pass.INDEX].ARGS[2] = "http://" .. backend
                    end
                end
            end
        end
        -- update nested locations
        if v.locations then
            for _, loc in ipairs(v.locations) do
                local r, m = update_location(loc)
                if not r then return nil, m end
            end 
        end 
        -- update if in location
        if v.ifs then
            for _, lif in ipairs(v.ifs) do
                local r, m = update_location(lif)
                if not r then return nil, m end
            end 
        end 
        return num
    end 
    -- here v is server or include file in server block
    for _, loc in ipairs(v.locations) do
        local r, m = update_location(loc)
        if not r then return nil, m end
    end 
    return true, num
end


local function check_json_format(json_str)
    local lbm_map = { rrb = 1, iphash = 1}
    local default_conf = {}
    local hc_default = { interval = "3000", rise = "1", fall = "2",
                         timeout = "5000", default_down = "false",
                         uri = "/status.taobao", expect_codes = "http_2xx" }
    local fcm_default = { {name = "nick", conf = {name = "_nk_"}} }
    default_conf.hc = hc_default
    default_conf.fcm = fcm_default
    default_conf.lbm = "rrb"
    local cjson = require("cjson.safe")
    local v, msg = cjson.decode(json_str)
    if not v then return nil, "decode json failed: "..(msg or "") end
    -- check format
    if type(v) ~= 'table'            then
        return nil, "wrong format"
    end 
    if type(v.version) ~= 'string' then
        return nil, "no version or bad format"
    end
    local version, major, min = gcf.version_to_num(v.version)
    if not version then return nil, major end
    if major ~= gcf.VERSION_MAJOR then
        return nil, "version is not compatible, only support " .. gcf.VERSION_SUP
    end
    if not tonumber(v.sid) then
        return nil, "no strategy id or wrong format"
    end 
    if not icheck(v.hosts, 'string') then
        return nil, "no server name or wrong format"
    end 
    if v.lbm == nil then v.lbm = default_conf.lbm end
    if not icheck(v.lbm, 'string')   then
        return nil, "wrong format of load balance method"
    end 
    if lbm_map[v.lbm] == nil         then
        return nil, "unknown type of load balance"
    end 
    if (not icheck(v.asa, 'array')) or #v.asa <= 0 then
        return nil, "no A server address or wrong format"
    end
    if type(v.bs) ~= 'table' or #v.bs <= 0 then
        return nil, "no B servers"
    end
    local bs_max = 9
    if #v.bs > bs_max then return nil, "too many bservers" end
    for _, b in ipairs(v.bs) do
        local seq = tonumber(b.seq)
        if not seq then
            return nil, "no sequence number in bserver"
        end
        if seq <= 0 or seq > bs_max then 
            return nil, "invalid sequence number: "..seq
        end
        if (not icheck(b.sa, 'array')) or #b.sa <= 0 then
            return nil, "no B server adderss or wrong format"
        end
        if b.fcm == nil                then b.fcm = default_conf.fcm  end
        if type(b.fcm) ~= 'table' or #b.fcm <= 0 then
            return nil, "wrong format of flow control method"
        end
        for _, fcm in ipairs(b.fcm) do
            if fcm.name == "ip" and fcm.conf then
                if fcm.conf.ips and not icheck(fcm.conf.ips, 'array') then
                    return nil, "wrong format of ip list"
                end 
                if fcm.conf.geo and not icheck(fcm.conf.geo, 'array') then
                    return nil, "wrong format of geo" 
                end 
                local ips
                if fcm.conf.ips then
                    for _, a in ipairs(fcm.conf.ips) do
                        if not string.match(a, "%S+%-%S+") then
                            return nil, "wrong format of "..a.." in ip list"
                        end
                    end
                    ips = fcm.conf.ips
                end
                if fcm.conf.geo then
                    for _, city in ipairs(fcm.conf.geo) do
                        local f = io.open(gcf.get_geo_city_fullname(city), "r")
                        if not f then
                            return nil, "city "..city.." is not support now"
                        else
                            f:close()
                        end
                    end
                    ips = fcm.conf.geo
                end
                if not ips then return nil, "no ip list in conf" end
            end
            if fcm.name == "nick" then
                if type(v.tair) ~= 'table' then
                    return nil, "no tair configure"
                end
                if not icheck(v.tair.tsa, 'array') or #v.tair.tsa <= 0 then
                    return nil, "no tair server address"
                end 
                if not icheck(v.tair.tgroup, 'string') then
                    return nil, "no tair group or wrong format"
                end 
                if not tonumber(v.tair.tns) then
                    return nil, "no tair namespace or wrong format"
                end 
            end
        end
    end
    if v.uris ~= nil then
        if (not icheck(v.uris, 'array')) or #v.uris < 1 then
            return nil, "wrong format of uri list"
        end 
    end
    if v.hc == "off" then
        v.hc = nil
    elseif v.hc == nil then
        v.hc = default_conf.hc
    else
        if not icheck(v.hc, 'table') then
            return nil, "wrong format of health check"
        end 
    end
    if v.hc then
       if v.hc.interval == nil then v.hc.interval = default_conf.hc.interval end
       if v.hc.rise == nil     then v.hc.rise = default_conf.hc.rise end
       if v.hc.fall == nil     then v.hc.fall = default_conf.hc.fall end
       if v.hc.timeout == nil  then v.hc.timeout = default_conf.hc.timeout end
       if tonumber(v.hc.interval) == nil then
           return nil, "bad value of health check interval"
       end
       if tonumber(v.hc.rise) == nil     then
           return nil, "bad value of health check rise times"
       end
       if tonumber(v.hc.fall) == nil     then
           return nil, "bad value of health check fall times"
       end
       if tonumber(v.hc.timeout) == nil  then
           return nil, "bad value of health check timeout"
       end
       if tonumber(v.hc.timeout) < 1000  then
           return nil, "too small timeout for health check"
       end
       if v.hc.uri == nil          then
           v.hc.uri = default_conf.hc.uri
       end
       if v.hc.default_down == nil then
           v.hc.default_down = default_conf.hc.default_down
       end
       if v.hc.expect_codes == nil then
           v.hc.expect_codes = default_conf.hc.expect_codes
       end
    end
    if arg_dowhat == 'init' and arg_status == 'json' then
        local engine_conf, msg = engine_creator.create_engine_conf(v, false)
        if not engine_conf then return nil, msg end
    end
    return true, v
end


local function save_gray_json(json_string, sid)
    local fn = gcf.get_gray_json_fullname(sid)
    local f, m = io.open(fn, "w")
    if f == nil then
        return ecode_env_exception, m
    end
    f:write(json_string)
    f:close()
    return ecode_success
end


local function check_servers_gray_status(core_cf, jservers)
    if arg_dowhat ~= 'init' and arg_dowhat ~= 'test' then
        return true
    end
    for name in pairs(jservers.names) do
        local srv = core_cf.ngx_conf_server_name_map[name] 
        if srv == nil then
            return nil, "server "..name.." not defined in configure"
        end
        if srv.includes then
            for _, inc in ipairs(srv.includes) do
                local sid = string.match(inc.PARSE_CONF.file_name,
                                         "gray_(%d+)/gray_srv_inc%.conf$")
                if sid then
                    if arg_dowhat == 'init' then
                        return nil, "strategy "..sid.." is not cleared"
                    end
                end
            end
        end
    end
    local http_inc_fullname = gcf.get_http_inc_fullname(jservers.sid)
    local ngx_conf_http = core_cf.ngx_conf_http
    if ngx_conf_http.includes then
        for _, inc in ipairs(ngx_conf_http.includes) do
            if inc.PARSE_CONF.file_name == http_inc_fullname then
                return nil, "strategy "..jservers.sid.." is not cleared in http"
            end
        end
    end
    return true
end


-- top level api
local function parse_json_from(source, in_file)
    local jservers = { names = {}, geos = {}, ups = {} }
    local json_str = source
    if in_file then
        local f, m = io.open(source, "r")
        if f == nil then return ecode_env_exception, m end
        json_str = f:read("*all")
        f:close()
    end
    local rt, msg = check_json_format(json_str)
    if not rt then return ecode_retry_after_modify, msg end
    local v = msg
    jservers.asn = get_server_name(v.sid)
    jservers.ups[jservers.asn] = v.asa
    for _, b in ipairs(v.bs) do
        jservers.ups[get_server_name(v.sid, b.seq)] = b.sa
        for _, fcm in ipairs(b.fcm) do
            if fcm.name == "ip" and fcm.conf then
                if jservers.geos[gcf.get_geo_block_name(v.sid, b.seq)] then
                    return ecode_retry_after_modify,
                           "duplicate flow control method in bserver "..b.seq
                end
                jservers.geos[gcf.get_geo_block_name(v.sid, b.seq)] = fcm.conf
            end
            if fcm.name == "nick" then
                if not jservers.etair then
                    jservers.etair = {}
                    jservers.etair.name = gcf.get_etair_name(v.sid)
                    jservers.etair.tsa = v.tair.tsa
                    jservers.etair.tgroup = v.tair.tgroup
                    jservers.etair.tns = v.tair.tns
                end
            end
        end
    end
    local num = 0
    for name in string.gmatch(v.hosts, "%S+") do 
        if not string.match(name, "%S+:%S+") then
            name = name..":".."80"
        end
        jservers.names[name] = 1
        num = num + 1
    end
    if num == 0 then return ecode_retry_after_modify, "no host in json" end
    jservers.num = num
    jservers.sid = v.sid
    jservers.conf = v
    return ecode_success, jservers
end


local function create_srv_inc_file_conf(sid, jservers)
    local srv_inc_file_conf = {}
    -- gray_engine
    -- set $gray_sid sid
    ngx_conf_insert_head(srv_inc_file_conf, {ARGS={'set',
                                            "$" .. gcf.gray_sid_var_name, sid}})
    if cell and not jservers.conf.uris then
        ngx_conf_insert_head(srv_inc_file_conf, {ARGS={'access_by_lua_file',
                                                gcf.engine_script_fullname}})
    end
    -- set $gray_server_name ""
    --[[ngx_conf_insert_head(srv_inc_file_conf, {ARGS={'set', gray_server_var_name,
                                             gcf.get_server_name(sid)}})]]
    if jservers.conf.uris then
        for _, uri in ipairs(jservers.conf.uris) do
            local directive = { ARGS = {'location', '=', uri} }
            local location = {}
            if not cell then
                ngx_conf_insert_head(location, {ARGS={'set', gray_server_var_name,
                                     get_server_name(sid)}})
                ngx_conf_insert_tail(location, {ARGS={'set', gray_uri_var_name,
                                     "$uri"}})
            end
            ngx_conf_insert_head(location, {ARGS={'access_by_lua_file',
                                            gcf.engine_script_fullname}})
            ngx_conf_insert_head(location, {ARGS={'proxy_pass',
                                 gray_proxy_pass_value}})
            directive.BLOCK = location
            ngx_conf_insert_head(srv_inc_file_conf, directive)
        end
    --[[else
        -- althought there maybe tair location exist,
        -- it's ok, because access phase never called in subrequest
        ngx_conf_insert_head(srv_inc_file_conf, {ARGS={'access_by_lua_file',
                                                 gcf.engine_script_fullname}})]]
    end
    -- tair location
    --[[if jservers.etair then
        local directive = { ARGS = {'location', gcf.get_gray_tair_loc_name(sid)} }
        local location = {}
        ngx_conf_insert_head(location, {ARGS={'internal'}})
        ngx_conf_insert_head(location, {ARGS={'tair_java', 'on'}})
        ngx_conf_insert_head(location, {ARGS={'tair_methods', 'get'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_key', '$arg_key'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_method', '$arg_method'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_area', '$arg_area'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_value', '$arg_value'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_version', '$arg_version'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_exptime', '$arg_exptime'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_count', '$arg_count'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_value_type', '$arg_type'}})
        ngx_conf_insert_head(location, {ARGS={'set', '$tair_init_value', '$arg_init'}})
        local args = {'tair_pass'}
        for _, sa in ipairs(jservers.etair.tsa) do
            args[#args + 1] = sa
        end
        args[#args + 1] = jservers.etair.tgroup
        ngx_conf_insert_head(location, {ARGS=args})
        directive.BLOCK = location
        ngx_conf_insert_head(srv_inc_file_conf, directive)
        end]]
    return true, srv_inc_file_conf
end


local function create_http_inc_file_conf(sid, jservers)
    local http_inc_file_conf = {}
    -- init by lua, only when no other
    --local directive = {}
    -- require gray_engine
    --local init_script = string.format("\"require('%s');\"",
    --                                  gcf.get_engine_module_name(sid))
    --if jservers.etair then
    --    init_script = string.format("%s require('%s');\"",
    --                                init_script, gcf.get_tair_module_name(sid))
    --else
    --    init_script = init_script .. "\""
    --end
    --local args = {'init_by_lua_file', gcf.engine_init_script_fullname}
    --directive.ARGS = args
    --ngx_conf_insert_head(http_inc_file_conf, directive)]]
    -- etair block
    if jservers.etair then
        local directive = { ARGS = {'etair', jservers.etair.name} }
        local etair = {}
        ngx_conf_insert_head(etair, {ARGS={'group_name',jservers.etair.tgroup}})
        for _, s in ipairs(jservers.etair.tsa) do
            local config_server = {'config_server'}
            config_server[#config_server + 1] = s
            ngx_conf_insert_head(etair, {ARGS=config_server})
        end
        directive.BLOCK = etair
        ngx_conf_insert_head(http_inc_file_conf, directive)
    end
    local js = jservers.conf
    -- upstream blocks
    for name, value in pairs(jservers.ups) do
        local directive = { ARGS = {'upstream', name} }
        local upstream = {}
        if js.lbm == "iphash" then
            ngx_conf_insert_head(upstream, {ARGS={'ip_hash'}})
        end
        local need_hc = true
        -- don't check health for localhost
        if name == get_server_name(sid) then
            if #value == 1 and string.match(value[1], "^127%.0%.0%.1") then
                need_hc = false
            end
        end
        if js.hc and need_hc then
            local args = { 'check', 'interval='..js.hc.interval,
                           'rise='..js.hc.rise, 'fall='..js.hc.fall,
                           'timeout='..js.hc.timeout,
                           'default_down='..js.hc.default_down, 'type=http' }
            ngx_conf_insert_head(upstream, {ARGS=args})
            args = { 'check_http_send',
                     string.format("\"GET %s HTTP/1.0\\r\\n\\r\\n\"", js.hc.uri)
                   }
            ngx_conf_insert_head(upstream, {ARGS=args})
            args = { 'check_http_expect_alive', js.hc.expect_codes }
            ngx_conf_insert_head(upstream, {ARGS=args})
        end
        for _, sa in ipairs(value) do
            local args = { 'server', sa }
            if js.hc and need_hc then args[#args + 1] = "max_fails=0" end
            ngx_conf_insert_head(upstream, {ARGS=args})
        end
        -- backup servers
        if name ~= get_server_name(sid) and js.lbm == 'rrb' then
            for _, sa in ipairs(js.asa) do
                local args = { 'server', sa }
                if js.hc then args[#args + 1] = "max_fails=0" end
                args[#args + 1] = "backup"
                ngx_conf_insert_head(upstream, {ARGS=args})
            end
        end
        directive.BLOCK = upstream
        ngx_conf_insert_head(http_inc_file_conf, directive)
    end
    -- geo blocks
    for name, value in pairs(jservers.geos) do
        local directive = { ARGS = {'geo', "$" .. name} }
        local geo = {}
        ngx_conf_insert_head(geo, {ARGS={'ranges'}})
        ngx_conf_insert_head(geo, {ARGS={'default', '0'}})
        if value.ips then
            for _, ip in ipairs(value.ips) do
                ngx_conf_insert_head(geo, {ARGS={ip, '1'}})
            end
        end
        if value.geo then
            for _, city in ipairs(value.geo) do
                ngx_conf_insert_head(geo, {ARGS={'include',
                                           gcf.get_geo_city_fullname(city)}})
            end
        end
        directive.BLOCK = geo
        ngx_conf_insert_head(http_inc_file_conf, directive)
    end
    return true, http_inc_file_conf
end


local function update_server_blocks(core_cf, jservers)
    local servers = core_cf.ngx_conf_servers
    local new_servers = {}
    local original_file_map = {}
    for _, s in ipairs(servers) do
        local num = 0
        local names = {}
        if s.server_name then
            for name in pairs(s.server_name.PARSE_CONF.names) do
                local unique_name = s.UNIQUE_NAME_MAP[name]
                if jservers.names[unique_name] == 1 then
                    s.server_name.PARSE_CONF.names[name] = 0
                    names[name] = 1
                    jservers.names[unique_name] = 0
                    num = num + 1
                end
            end
        end
        if num > 0 then
            -- is server in include file?
            if s.PARENT.INCLUDE then
                local directive = s.PARENT.PARENT[s.PARENT.INDEX]
                local file_name = directive.ARGS[2]
                local original_file_name = file_name 
                local new_file_name = file_name
                if string.sub(file_name, 1, 1) ~= "/" then
                    original_file_name = ngx_conf_prefix .. file_name
                else
                    new_file_name = string.sub(file_name, #ngx_conf_prefix + 1)
                end
                new_file_name = string.gsub(new_file_name, "/", "_")
                new_file_name = gcf.get_gray_sid_path(jservers.sid) .. new_file_name
                original_file_map[new_file_name] = original_file_name
                directive.ARGS[2] = new_file_name
                directive.RECREATE = 1
            end
            if num == #s.server_name.ARGS - 1 then
                s.GRAY = true
            else
                local ns_directive = nginx_parser.copy_directive(s.PARENT[s.INDEX])
                -- add the new directive after this server  
                nginx_parser.ngx_conf_directive_insert_tail(s.PARENT[s.INDEX],
                                                            ns_directive)
                local ns = ns_directive.BLOCK
                ns.PARENT = s.PARENT
                ns.GRAY = true
                -- new server_name directive for ns
                local directive = {}
                local args = {}
                table.insert(args, "server_name")
                for name in pairs(names) do
                    table.insert(args, name)
                end
                directive.ARGS = args
                ns[s.server_name.INDEX] = directive
                ns.server_name = nil
                -- new listen directive for ns
                if s.listen then
                    for _, listen in ipairs(s.listen) do
                        local directive = {}
                        local args = {}
                        table.insert(args, "listen")
                        table.insert(args, listen.ARGS[2])
                        directive.ARGS = args
                        ns[listen.INDEX] = directive
                    end
                end
                ns.listen = nil
                -- modify server_name directive for s
                args = {}
                table.insert(args, "server_name")
                for n, v in pairs(s.server_name.PARSE_CONF.names) do
                    if v == 1 then
                        table.insert(args, n)
                    end
                end
                s[s.server_name.INDEX].ARGS = args
                table.insert(new_servers, ns)
            end
        end
    end
    -- insert new servers
    for _, ns in ipairs(new_servers) do
        table.insert(servers, ns)
    end
    local gray_all = not jservers.conf.uris
    -- update locations(both server and inc file)
    for _, s in ipairs(servers) do
        if s.GRAY then
            if s.locations and gray_all then 
                local r, m = update_server_locations(s)
                if not r then return nil, m end
            end
            if s.includes then
                for _, inc in ipairs(s.includes) do
                    if arg_dowhat == 'test' then
                        local sid = string.match(inc.PARSE_CONF.file_name,
                                             "gray_(%d+)/gray_srv_inc%.conf$")
                        if sid then
                            s[inc.INDEX].DELETE = true
                        end
                    end
                end
            end
            if s.includes and gray_all then
                for _, inc in ipairs(s.includes) do
                    if (not s[inc.INDEX].DELETE) and inc.locations then
                        local r, m = update_server_locations(inc)
                        if not r then return nil, m end
                        -- if exist proxy_pass directive
                        if m > 0 then
                            local file_name = inc.PARSE_CONF.file_name
                            local original_fullname = file_name 
                            local new_fullname = file_name
                            if string.sub(file_name, 1, 1) ~= "/" then
                                original_fullname = ngx_conf_prefix .. file_name
                            else
                                new_fullname = string.sub(file_name,
                                                          #ngx_conf_prefix + 1)
                            end
                            new_fullname = string.gsub(new_fullname, "/", "_")
                            new_fullname = gcf.get_gray_sid_path(jservers.sid) .. 
                                            new_fullname
                            original_file_map[new_fullname] = original_fullname
                            s[inc.INDEX].ARGS[2] = new_fullname
                            s[inc.INDEX].RECREATE = 1
                        end
                    end
                end
            end
            -- insert include gray_srv_inc.conf line
            local directive = {} 
            local args = {}
            table.insert(args, "include")
            table.insert(args, gcf.get_srv_inc_fullname(jservers.sid))
            directive.ARGS = args
            local rc, msg = create_srv_inc_file_conf(jservers.sid, jservers)
            if not rc then return nil, msg end
            directive.NGX_CONF = msg
            directive.RECREATE = true
            ngx_conf_insert_head(s, directive)
        end
    end
    core_cf.original_file_map = original_file_map
    return true
end


local function check_ngx_conf(cf, jservers)
    -- TODO check proxy_pass $ups
    if arg_dowhat == 'init' or arg_dowhat == 'test' then
        --  check access_by_lua*
        if cf.http.access_by_lua then
            return nil,
                   "A/B test can not used with access_by_lua(_file) directive"
        end
        for name in pairs(jservers.names) do
            local srv = cf.ngx_conf_server_name_map[name] 
            if srv.access_by_lua then
                if arg_dowhat == 'init' then
                    return nil,
                        "A/B test can not used with access_by_lua(_file) directive"
                else
                    for _, d in ipairs(srv.access_by_lua) do
                        if d.ARGS[2] ~= gcf.engine_script_fullname then
                            return nil,
                                "A/B test can not used with access_by_lua(_file) directive"
                        end
                    end
                end
            end
        end
        -- check include files (including server blocks or location blocks)
        for _, inc in ipairs(cf.includes) do
            local file_name = inc.PARSE_CONF.file_name
            if string.byte(file_name, 1, 1) == '/' then
                if string.sub(file_name, 1, #ngx_conf_prefix) ~=
                   ngx_conf_prefix and (inc.servers or inc.locations) then
                    return nil, file_name .. " must located in " ..
                           ngx_conf_prefix
                end
            end
        end
        -- check proxy_pass
        for name in pairs(jservers.names) do
            local srv = cf.ngx_conf_server_name_map[name] 
            if not srv.proxy_passes then
                return nil, "it seems to be no proxy_pass directive in server: " .. 
                       name
            end
            if cell then
                local ups_name = cell_local_ups -- "local"
                if srv.APP then
                    ups_name = srv.APP .. "_" .. cell_local_ups
                end
                local ups 
                if cf.http.upstreams then
                   ups = cf.http.upstreams[ups_name]
                end
                if not ups then
                    return nil, "upstream " .. ups_name .. " not exist"
                end
                if not ups.servers then
                    return nil, "not server in upstream " .. ups_name
                end
                local num_of_sa = 0
                for k, v in pairs(ups.servers) do
                    num_of_sa = num_of_sa + 1
                end
                if num_of_sa ~= #jservers.conf.asa then
                    return nil, "the number of server in upstream " ..
                           ups_name .. " not the same with asa"
                end
                for _, sa in ipairs(jservers.conf.asa) do
                    if not ups.servers[sa] then
                        return nil, "server list in upstream " ..
                               ups_name .. " not the same with asa"
                    end
                end
            end

            if not jservers.conf.uris then
                for _, pps in ipairs(srv.proxy_passes) do
                    if (not cell) and (not (arg_dowhat == 'test'
                       and pps.ARGS[2] == gray_proxy_pass_value)) then
                        if not pps.PARSE_CONF.host then
                            return nil, "only support http protocol in proxy_pass"
                        end
                        if string.match(pps.PARSE_CONF.host, "%$") then
                            return nil, "not support variables in proxy_pass"
                        end
                        local ups 
                        if cf.http.upstreams then
                           ups = cf.http.upstreams[pps.PARSE_CONF.host]
                        end
                        if ups then
                            if not ups.servers then
                                return nil, "not server in upstream " .. 
                                       pps.PARSE_CONF.host
                            end
                            local num_of_sa = 0
                            for k, v in pairs(ups.servers) do
                                num_of_sa = num_of_sa + 1
                            end
                            if num_of_sa ~= #jservers.conf.asa then
                                return nil, "the number of server in upstream " ..
                                      pps.PARSE_CONF.host .. " not the same with asa"
                            end
                            for _, sa in ipairs(jservers.conf.asa) do
                                if not ups.servers[sa] then
                                    return nil, "server list in upstream " ..
                                      pps.PARSE_CONF.host .. " not the same with asa"
                                end
                            end
                        else
                            if #jservers.conf.asa > 1 then
                                return nil, "unknown proxy_pass server address " ..
                                       pps.PARSE_CONF.host
                            end
                            if pps.PARSE_CONF.host ~= jservers.conf.asa[1] then
                                return nil, "unknown proxy_pass server address " ..
                                       pps.PARSE_CONF.host
                            end
                        end
                    end
                end
            end
        end
    end
    return true
end


-- top level api
local function check_nginx_status()
    -- check if somebody is editting the configure file
    local ngx_conf_swp_name = string.gsub(ngx_conf_name, "/([^/]+)$", "/.%1.swp")
    if utils.is_file_exist(ngx_conf_swp_name) then
        return ecode_retry_later, ngx_conf_swp_name ..
               " is found, someone may be editting the nginx configure file"
    end
    -- check if nginx is running
    local status, data = nginx_operator.check_nginx_status() 
    if status == 'upgrading' then
        return ecode_retry_later, "nginx is upgrading"
    elseif status == 'stopped' then
        return ecode_env_exception, "nginx is not running"
    elseif status == 'running' then
        ngx_pid = data
    else
        return ecode_env_exception, "unknown status of nginx: "..(data or "")
    end
    -- check nginx configure file
    local ecode, msg = nginx_operator.operate_nginx('test', ngx_conf_name) 
    if ecode ~= ecode_success then
        return ecode_env_exception, "test nginx configure file failed: "..
               (msg or "")
    end
    return ecode_success
end


local function check_sid_disk_status(sid, cf)
    local ecode = ecode_success
    -- check include lines in main conf
    local srv_inc, http_inc, hosts, msg
    if cf.includes then
        for _, inc in ipairs(cf.includes) do
            if inc.PARSE_CONF.file_name == gcf.get_srv_inc_fullname(sid) then
                srv_inc = true
            elseif inc.PARSE_CONF.file_name == gcf.get_http_inc_fullname(sid) then
                http_inc = true
            end
        end
    end
    if srv_inc ~= http_inc then
        if srv_inc then msg = "http include file not exist" end
        if http_inc then msg = "server include file not exist" end
        return ecode_env_exception, msg .. " in strategy " .. sid
    end
    -- check gray strategy directory
    local f = io.open(gcf.get_engine_conf_fullname(sid), "r")
    if f then
        if not srv_inc then
            f:close()
            return ecode_env_exception, "no gray include file line in main conf"
        end
        for line in f:lines() do
            if string.match(line, "[ ]*%s*%[\"enable\"%] = false") then
                msg = "stop"
                ecode = ecode_success
                break
            elseif string.match(line, "[ ]*%s*%[\"enable\"%] = true") then
                msg = "start"
                ecode = ecode_success
                break
            else 
                ecode = ecode_env_exception
                msg = "unknown gray strategy status"
            end
        end
        f:close()
        return ecode, msg
    end
    if srv_inc then
        return ecode_env_exception, "not gray strategy files exist"
    end
    if utils.is_file_exist(gcf.get_gray_json_fullname(sid)) then
        msg = "json"
    else
        msg = "no"
    end
    return ecode_success, msg
end


local function check_sid_mem_status(sid)
    local ecode = ecode_success
    local hosts, msg
    -- check in memory
    local engine_conf = package.loaded[gcf.get_engine_conf_module_name(sid)]
    if type(engine_conf) == 'table' then
        if engine_conf.enable == false then
            msg = 'stop'
            hosts = engine_conf.hosts
        elseif engine_conf.enable == true then
            msg = 'start'
            hosts = engine_conf.hosts
        else
            ecode = ecode_env_exception
            msg = "unknown strategy status"
        end
    else
        msg = 'no'
    end
   return ecode, msg, hosts 
end


local function check_sid_gray_status(sid, cf)
    local ecode_mem, msg_mem, hosts = check_sid_mem_status(sid)
    local ecode_disk, msg_disk
    if cf then
        ecode_disk, msg_disk = check_sid_disk_status(sid, cf)
        if ecode_disk ~= ecode_success or ecode_mem ~= ecode_success then
            return ecode_env_exception, "Disk: " .. msg_disk .. " Mem: " .. msg_mem
        end
        if msg_disk == 'json' and msg_mem == 'no' then
            return ecode_success, msg_disk
        end
        if msg_disk ~= msg_mem then
            return ecode_env_exception, "different gray status. Disk: " ..
                                        msg_disk .. " Mem: " .. msg_mem
        end
    end
    return ecode_mem, msg_mem, hosts
end


-- top level api
local function check_gray_env(sid)
    local rt, msg = utils.test_permission(gcf.ngx_conf_prefix, 'rwx')
    if not rt then return ecode_env_exception, msg end
    rt, msg = utils.test_permission(gcf.gray_strategy_pathname, 'rwx')
    if not rt then return ecode_env_exception, msg end
    rt, msg = utils.is_file_exist(gcf.engine_script_fullname)
    if not rt then return ecode_env_exception, msg end
    rt, msg = utils.is_file_exist(gcf.engine_init_script_fullname)
    if not rt then return ecode_env_exception, msg end
    if arg_dowhat == 'clear' and utils.is_dir(gcf.get_gray_sid_path(sid)) then
        rt, msg = utils.test_permission(gcf.get_gray_sid_path(sid), 'rwx')
    end
    if not rt then return ecode_env_exception, msg end
    return ecode_success
end


local function parse_ngx_conf()
    --[[if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        return ecode_success
        end]]
    local core_cf = nginx_parser.create_ngx_cf(nginx_parser.CORE_MODULE,
                                               nginx_parser.MAIN_CONF)
    local rc, msg = nginx_parser.ngx_conf_parse(core_cf, ngx_conf_name)
    if not rc then return ecode_env_exception, msg end
    nginx_parser.get_server_name_map(core_cf)
    return ecode_success, core_cf
end


-- top level api
local function check_strategy_status(sid, cf)
    local ecode, msg = check_sid_gray_status(sid, cf)
    local sid_status = msg
    -- skip it when do clear
    if arg_dowhat == 'clear' then
        if ecode ~= ecode_success then
            return ecode_again
        end
        if sid_status == 'no' then
            return ecode_success
        end
        if sid_status == 'json' then
            os.remove(gcf.get_gray_json_fullname(sid))
            return ecode_success
        end
    end
    if ecode ~= ecode_success then
        return ecode, msg
    end
    if arg_dowhat == 'test' then
        if sid_status ~= 'no' and sid_status ~= 'json' then
            return ecode_retry_after_modify, "strategy "..sid..
                   " is already running, clear before create a new one"
        end
    end
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        if sid_status == 'no' or sid_status == 'json' then
            return ecode_retry_after_modify, "strategy not exist"
        end
        if sid_status == arg_dowhat then return ecode_success end
    end
    if arg_dowhat == 'init' then
        if sid_status == arg_status then return ecode_success end
        if sid_status == 'no' then
            return ecode_retry_after_modify, "json file no exist"
        end
        if sid_status == 'start' or sid_status == 'stop' then
            return ecode_retry_after_modify, "strategy " .. sid ..
                   " is already running, its status is " .. sid_status
        end
    end
    return ecode_again
end


-- top level api
local function update_ngx_conf(sid, jservers, core_cf)
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        return ecode_success
    end
    local rc, msg = check_servers_gray_status(core_cf, jservers)
    if not rc then return ecode_retry_after_modify, msg end
    rc, msg = check_ngx_conf(core_cf, jservers)
    if not rc then return ecode_env_exception, msg end
    local gray, original_file_name
    local ngx_conf_http = core_cf.ngx_conf_http
    local ngx_conf_servers = core_cf.ngx_conf_servers
    -- TODO check server_name buy.taobao.com *.buy.taobao.com
    if arg_dowhat == 'init' or arg_dowhat == 'test' then
        rc, msg = update_server_blocks(core_cf, jservers)
        if not rc then return ecode_env_exception, msg end
       -- insert http include file
        local directive = {}
        directive.ARGS = {"include", gcf.get_http_inc_fullname(jservers.sid)}
        rc, msg = create_http_inc_file_conf(sid, jservers)
        if not rc then return ecode_retry_after_modify, msg end
        directive.NGX_CONF = msg
        directive.RECREATE = true
        ngx_conf_insert_head(ngx_conf_http, directive)
    elseif arg_dowhat == 'clear' then
        local rt, msg = get_original_file_map(sid)
        if not rt then return ecode_env_exception, msg end
        local original_file_map = msg
        local original_file_name
        -- delete gray http include file
        if ngx_conf_http.includes then
            for _, inc in ipairs(ngx_conf_http.includes) do
                if inc.PARSE_CONF.file_name == gcf.get_http_inc_fullname(sid) then
                    ngx_conf_http[inc.INDEX].DELETE = 1
                end
            end
        end
        for _, srv in ipairs(ngx_conf_servers) do
            gray = false            
            -- delete gray server include file
            if srv.includes then
                for _, inc in ipairs(srv.includes) do
                    if inc.PARSE_CONF.file_name == gcf.get_srv_inc_fullname(sid) then
                        gray = true
                        srv[inc.INDEX].DELETE = 1
                    end
                end
            end
            if gray then
                -- is the server block in include file?
                if srv.PARENT.INCLUDE then
                    local directive = srv.PARENT.PARENT[srv.PARENT.INDEX]
                    local file_name = srv.PARENT.PARSE_CONF.file_name
                    original_file_name = original_file_map[file_name]
                    if not original_file_name then
                        return ecode_env_exception,
                               "can not get original filename for file: "..file_name
                    end
                    directive.ARGS[2] = original_file_name
                    -- if the original server file exist, just use it;
                    -- otherwise, recreate
                    if not utils.is_file_exist(original_file_name) then
                        directive.RECREATE = 1
                    end
                end
                if not cell then
                    -- delete access_by_lua_file
                    if srv.access_by_lua then
                        for _, d in ipairs(srv.access_by_lua) do
                            if d.ARGS[1] == "access_by_lua_file" and
                               d.ARGS[2] == gcf.engine_script_fullname then
                                d.DELETE = true
                            end
                        end
                    end
                    -- update server include files and include line
                    if srv.includes then
                        for _, inc in ipairs(srv.includes) do
                            original_file_name = original_file_map[inc.PARSE_CONF.file_name]
                            local directive = srv[inc.INDEX]
                            if original_file_name then
                                directive.ARGS[2] = original_file_name
                                -- if the original server include file exist, just use it;
                                -- otherwise, recreate.
                                if not utils.is_file_exist(original_file_name) then
                                    local rc, msg = update_server_locations(inc)
                                    if not rc then return ecode_env_exception, msg end
                                    directive.RECREATE = 1
                                end
                            end
                        end
                    end
                    -- update locations
                    if srv.locations then
                        local rc, msg = update_server_locations(srv)
                        if not rc then return ecode_env_exception, msg end
                    end
                end
            end
        end
    else
        return ecode_success
    end
    return ecode_success, core_cf
end


-- top level api
local function update_gray_engine(sid, jservers, gray_enable)
    local engine_conf, msg
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        engine_conf = package.loaded[gcf.get_engine_conf_module_name(sid)]
        engine_conf.enable = gray_enable
    end
    if arg_dowhat == 'init' or arg_dowhat == 'test' then
        engine_conf, msg = engine_creator.create_engine_conf(jservers.conf,
                                                             gray_enable)
        if not engine_conf then return ecode_retry_after_modify, msg end
    end
    return ecode_success, engine_conf
end


local function backup_original_conf(sid)
    local rt, msg
    local target_path = gcf.gray_backup_pathname
    if arg_dowhat == 'init' or arg_dowhat == 'clear' then
        rt, msg = utils.copy(ngx_conf_name, target_path .. ngx_conf_basename)
        if not rt then return nil, msg end
    end
    if arg_dowhat == 'start' or arg_dowhat == 'stop' then
        rt, msg = utils.copy(gcf.get_engine_conf_fullname(sid),
                             target_path .. engine_conf_basename)
        if not rt then return nil, msg end
    end
    if arg_dowhat == 'clear' then
        if utils.is_dir(target_path .. gcf.get_gray_sid_dir(sid)) then
            utils.remove_dir(target_path .. gcf.get_gray_sid_dir(sid))
        end
        rt, msg = utils.copy_dir(gcf.get_gray_sid_path(sid), target_path)
        if not rt then return nil, msg end
        rt, msg = utils.copy(gcf.get_gray_json_fullname(sid), target_path .. 
                             gcf.get_gray_json_basename(sid))
        --if not rt then return nil, msg end
    end
    gray_admin_status.backupped = true
    return true
end


local function restore_original_conf(sid, success_created_files)
    gray_admin_status.restore_status = RESTORING
    -- save error configure files
    save_error_files(sid, success_created_files)
    if arg_dowhat == 'init' or arg_dowhat == 'test' then
        if utils.is_dir(gcf.get_gray_sid_path(sid)) then
            utils.remove_dir(gcf.get_gray_sid_path(sid))
        end
        if arg_dowhat == 'init' and
           gray_admin_status.phase >= ACTIVATE_NEW_CONF then
            utils.copy(gcf.gray_backup_pathname .. ngx_conf_basename,
                       ngx_conf_name)
        end
        os.remove(ngx_conf_test_name)
    end
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        utils.copy(gcf.gray_backup_pathname .. engine_conf_basename,
                     gcf.get_engine_conf_fullname(sid))
    end
    if arg_dowhat == 'clear' then
        if success_created_files then
            for fn, _ in pairs(success_created_files) do
                os.remove(fn)
            end
        end
        if gray_admin_status.phase >= ACTIVATE_NEW_CONF then
            utils.copy(gcf.gray_backup_pathname .. ngx_conf_basename, ngx_conf_name)
        end
        if gray_admin_status.phase >= WRITE_FILE_TO_TEST then
            utils.copy(gcf.gray_backup_pathname .. gcf.get_gray_json_basename(sid),
                       gcf.get_gray_json_fullname(sid))
            if not utils.is_dir(gcf.get_gray_sid_path(sid)) then
                utils.copy_dir(gcf.gray_backup_pathname .. gcf.get_gray_sid_dir(sid),
                               gcf.get_gray_sid_path(sid))
            end
        end
    end
    gray_admin_status.restore_status = RESTORED
    return true
end


local function original_file_map_to_file(original_file_map, sid)
    local f, m = io.open(gcf.get_original_file_map_fullname(sid), "w")
    if not f then return nil, m end
    for cur_file, ori_file in pairs(original_file_map) do
        f:write(string.format("%s %s\n", cur_file, ori_file))
    end
    f:close()
    return true
end


local function write_file_to_test(sid, core_cf, engine_conf)
    gray_admin_status.phase = WRITE_FILE_TO_TEST
    local rt = ecode_success
    local msg
    local success_created_files = {}
    global_success_files = success_created_files
    rt, msg = backup_original_conf(sid)
    if not rt then return ecode_env_exception, "backup failed : " .. msg end
    if arg_dowhat == 'init' or arg_dowhat == 'test' then
        rt, msg = utils.mkdir(gcf.get_gray_sid_path(sid))
        if not rt then return ecode_env_exception, msg end
        rt, msg = nginx_parser.ngx_conf_to_file(core_cf.ngx_conf, ngx_conf_test_name,
                                                success_created_files)
        if rt then
            rt, msg = original_file_map_to_file(core_cf.original_file_map, sid)
            if rt then
                rt, msg = engine_creator.engine_conf_to_file(engine_conf)
            end
            --[[if rt then
                -- engine script files
                rt, msg = engine_creator.engine_script_to_file(engine_conf) 
            end]]--
        end
    end
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        rt, msg = engine_creator.engine_conf_to_file(engine_conf)
    end
    if arg_dowhat == 'clear' then
        rt, msg = nginx_parser.ngx_conf_to_file(core_cf.ngx_conf, ngx_conf_test_name,
                                                success_created_files)
        if rt then
            utils.remove_dir(gcf.get_gray_sid_path(sid)) 
            os.remove(gcf.get_gray_json_fullname(sid))
        end
    end
    if not rt then
        restore_original_conf(sid, success_created_files)
        return ecode_env_exception, msg
    end
    return ecode_success, success_created_files
end


local function test_new_conf(sid, success_created_files)
    gray_admin_status.phase = TEST_NEW_CONF
    local conf_name = ngx_conf_test_name
    if arg_dowhat == 'stop' or arg_dowhat == 'start' then
        conf_name = ngx_conf_name
    end
    local ecode, msg = nginx_operator.operate_nginx("test", conf_name) 
    if ecode ~= ecode_success then
        restore_original_conf(sid, success_created_files)
        return ecode, "test nginx configure file failed: "..msg
    end
    -- test engine script
    --[[if arg_dowhat == 'init' then
        local engine_module = gcf.get_engine_module_name(sid)
        local engine_conf_module = gcf.get_engine_conf_module_name(sid)
        local engine_enable_module = gcf.get_engine_enable_module_name(sid)
        package.loaded[engine_module] = nil
        package.loaded[engine_conf_module] = nil
        package.loaded[engine_enable_module] = nil
        local function engine_test()
            local engine = require(engine_module)
            return engine.do_test()
        end
        local cur_newindex = getmetatable(_G).__newindex
        getmetatable(_G).__newindex = pre_newindex
        local r, msg = pcall(engine_test)
        getmetatable(_G).__newindex = cur_newindex
        package.loaded[engine_module] = nil
        package.loaded[engine_conf_module] = nil
        package.loaded[engine_enable_module] = nil
        if msg ~= "SUCCESS" then
            restore_original_conf(sid, success_created_files)
            return  ecode_env_exception, "create engine script failed: " .. msg
        end
    end]]--
    return ecode_success
end


local function activate_new_conf(sid, success_created_files)
    gray_admin_status.phase = ACTIVATE_NEW_CONF
    local rt = ecode_success
    local msg
    if arg_dowhat == 'test' then
        utils.remove_dir(gcf.get_gray_sid_path(sid))
        --os.remove(ngx_conf_test_name)
        return ecode_success
    end
    if arg_dowhat == 'init' then
        rt, msg = utils.copy(ngx_conf_test_name, ngx_conf_name)
    end
    if arg_dowhat == 'clear' then
        rt, msg = utils.copy(ngx_conf_test_name, ngx_conf_name)
        --if rt then
            -- test permission
            --rt, msg = utils.test_permission(gcf.get_gray_sid_path(sid), 'rwx')
        --end
    end
    if not rt then
        restore_original_conf(sid, success_created_files)
        return ecode_env_exception, msg
    end
    return ecode_success
end


local function reload_new_conf(sid, success_created_files)
    local function after_success_reload()
        local status, ecode, msg
        if arg_dowhat ~= 'stop' and arg_dowhat ~= 'start' then
            os.remove(ngx_conf_test_name)
        end
        if arg_dowhat == 'clear' then
            status = 'no'
        end
        if arg_dowhat == 'init' then
            status = arg_status
        end
        if arg_dowhat == 'stop' or arg_dowhat == 'start' then
            status = arg_dowhat
        end
        local max_times = gcf.gray_reload_retry_times
        local target_str = string.format('"status":"%s"', status)
        ecode = ecode_env_exception
        msg = "reload for strategy " .. sid .. " timeout"
        for i = 1, max_times do
            ngx.sleep(0.1)
            local resp = ngx.location.capture(string.format("%s?dowhat=look&sid=%s&from=gray", 
                                              gcf.ngx_query_loc, sid))
            if resp and resp.status == ngx.HTTP_OK then
                if resp.body and string.match(resp.body, target_str) then
                    ecode = ecode_success
                    msg = nil
                    break
                end
            end
        end
        return ecode, msg
    end
    local ecode, msg, ecode_a, msg_a
    if arg_dowhat == 'test' then
        return ecode_success
    end
    --[[ecode, msg = nginx_operator.operate_nginx("test", ngx_conf_name) 
    if ecode ~= ecode_success then
        restore_original_conf(sid, success_created_files)
        return ecode, msg
        end]]--
    gray_admin_status.phase = BEFORE_RELOAD_NEW_CONF
    ecode, msg = nginx_operator.operate_nginx('reload', ngx_pid)
    gray_admin_status.phase = AFTER_RELOAD_NEW_CONF
    if ecode == ecode_success then
        ecode, msg = after_success_reload(sid)
        if ecode == ecode_success then
            return ecode_success
        end
    end
    -- recover to original configure and reload
    restore_original_conf(sid, success_created_files)
    gray_admin_status.phase = BEFORE_RECOVER
    ecode_a, msg_a = nginx_operator.operate_nginx('reload', ngx_pid)
    gray_admin_status.phase = AFTER_RECOVER
    if ecode_a == ecode_success then 
        return ecode, msg
    end
    return ecode_fatal_error, "Reload error: "..msg.." Recover error: "..msg_a..
           " NGINX SERVER IS DOWN!! PLEASE CONTACT PE RIGHT NOW!!"
end


local function restful_admin()
    if arg_dowhat == 'init' and arg_status == 'json' then
        local json_string = ngx.var.request_body or ""
        if json_string == "" then
            return print_and_log(ecode_retry_after_modify, "no request body")
        end
        local rt, msg = check_json_format(json_string)
        if not rt then
            return print_and_log(ecode_retry_after_modify, msg)
        end
        local json_table = msg
        -- check sid arg_status
        --[[if utils.is_file_exist(gcf.get_gray_json_fullname(json_table.sid)) then
            return print_and_log(ecode_retry_after_modify, 
                                 "strategy " .. json_table.sid ..
                                 " exist, please clear it before recreate")
                                 end]]
        return print_and_log(save_gray_json(json_string, json_table.sid))
    elseif (arg_dowhat == 'init' and
            (arg_status == 'start' or arg_status == 'stop')) or
           arg_dowhat == 'stop' or arg_dowhat == 'start' or
           arg_dowhat == 'test' or arg_dowhat == 'clear' then
        local sid, jservers, source, in_file, ecode, msg
        local gray_enable = true
        -- check args
        if arg_dowhat ~= 'test' then
            sid = ngx.var.arg_sid
            if not tonumber(sid) then
                return print_and_log(ecode_retry_after_modify,
                                     "no strategy id or invalid format")
            end
            global_sid = sid
        end
        -- check nginx status
        ecode, msg = check_nginx_status()
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        if arg_dowhat ~= 'test' then
            if arg_dowhat == 'init' then
                source = gcf.get_gray_json_fullname(sid)
                in_file = true
                -- parse json
                ecode, msg = parse_json_from(source, in_file)
                if ecode ~= ecode_success then
                    return print_and_log(ecode, "decode json conf failed: "..
                                                (msg or "unknown"))
                end
                jservers = msg
                if sid ~= jservers.sid then
                    return print_and_log(ecode_retry_after_modify,
                                         "json file is broken, please upload again")
                end
                if arg_status == 'stop' then gray_enable = false end
            end
        else -- test
            source = ngx.var.request_body or ""
            if source == "" then
                return print_and_log(ecode_retry_after_modify, "no body") 
            end
             -- parse json
            ecode, msg = parse_json_from(source)
            if ecode ~= ecode_success then
                return print_and_log(ecode, "bad json format: "..
                                            (msg or "unknown"))
            end
            jservers = msg
            sid = jservers.sid
        end
        if arg_dowhat == 'stop' then gray_enable = false end
        -- TODO test permission
        ecode, msg = check_gray_env(sid)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        local ecode, msg = parse_ngx_conf(ngx_conf_name)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        local core_cf = msg
        -- is it a cell application?
        ecode, msg = check_cell_conf(core_cf, jservers, sid)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        if jservers then
            jservers.conf.cell = cell
            if cell then
                jservers.conf.cell_local_ups = cell_local_ups
            end
        end
        -- check strategy status
        ecode, msg = check_strategy_status(sid, core_cf)
        if ecode ~= ecode_again then return print_and_log(ecode, msg) end
        -- create new engine files(lua code)
        ecode, msg = update_gray_engine(sid, jservers, gray_enable)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        local engine_conf = msg
        -- create new nginx configure files
        ecode, msg = update_ngx_conf(sid, jservers, core_cf)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        local core_cf = msg
        ecode, msg = write_file_to_test(sid, core_cf, engine_conf)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        local success_created_files = msg
        -- test new configure
        ecode, msg = test_new_conf(sid, success_created_files)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        -- activate new configure
        ecode, msg = activate_new_conf(sid, success_created_files)
        if ecode ~= ecode_success then return print_and_log(ecode, msg) end
        -- reload new configure
        return print_and_log(reload_new_conf(sid, success_created_files))
   elseif arg_dowhat == 'look' then
        local sid = ngx.var.arg_sid
        if sid and not tonumber(sid) then
            return print_and_log(ecode_retry_after_modify, "invalid sid")
        end
        local msg, status, hosts
        local ecode = ecode_success
        if sid then
            ecode, msg, hosts = check_sid_gray_status(sid)
            if ecode == ecode_success then
                status = msg
                if status == 'no' and utils.is_file_exist(gcf.get_gray_json_fullname(sid)) then
                    status = 'json'
                end
                if  status == 'no' or status == 'json' then
                    msg = string.format('{"status":"%s","sid":"%s"}',
                                        status, sid)
                elseif status then
                    msg = string.format('{"host":"%s","status":"%s","sid":"%s"}',
                                        hosts or "error", status, sid)
                end
            end
            return print_and_log(ecode, msg, true)
        else
            local core_cf = nginx_parser.create_ngx_cf(nginx_parser.CORE_MODULE,
                                                       nginx_parser.MAIN_CONF)
            local rc, msg = nginx_parser.ngx_conf_parse(core_cf, ngx_conf_name)
            if not rc then return print_and_log(ecode_env_exception, msg) end
            nginx_parser.get_server_name_map(core_cf)
            local ngx_conf_servers = core_cf.ngx_conf_servers
            local msg = ""
            for _, srv in ipairs(ngx_conf_servers) do
                if srv.server_name then
                    local hosts = srv.server_name.ARGS[2]
                    for i, sn in ipairs(srv.server_name.ARGS) do
                        if i > 2 then
                            hosts = string.format("%s %s", hosts, sn)
                        end
                    end
                    local sid, cur_rst
                    if srv.includes then
                        for _, inc in ipairs(srv.includes) do
                            sid = string.match(inc.PARSE_CONF.file_name,
                                                     "gray_(%d+)/gray_srv_inc%.conf$")
                            if sid then break end
                        end
                    end
                    if sid then
                        local ecf = package.loaded[gcf.get_engine_conf_module_name(sid)]
                        if type(ecf) == 'table' then
                            if ecf.enable then status = 'start'
                            else status = 'stop' end
                            cur_rst = string.format('{"host":"%s","status":"%s","sid":"%s"}',
                                                    hosts, status, sid)
                        end
                    else
                        status = 'no'
                        cur_rst = string.format('{"host":"%s","status":"%s"}',
                                                hosts, status)

                    end
                    msg = msg .. cur_rst .. ","
                end
            end
            return print_and_log(ecode, msg, true)
        end
    elseif arg_dowhat == 'version' then
        return print_and_log("RAW", string.format('{"status":"SUCCESS","version":"%s"}',
                             gcf.VERSION), true)
    else
        return print_and_log(ecode_retry_after_modify, "unknown operation")
    end
end


local function lock()
    -- lua_shared_dict lua_lock 128k;
    local lua_lock = ngx.shared.lua_lock
    -- expired time is 30s
    local succ, err, forcible = lua_lock:add("lua_lock", 1, 20)
    if succ == true then
        return true
    else
        return false
    end
end
    

local function unlock()
    local lua_lock = ngx.shared.lua_lock
    lua_lock:delete("lua_lock")
end


local locked

if arg_dowhat ~= 'look' then
    locked = lock()
end

if locked == false then
    return print_and_log(ecode_retry_later, "someone is operating, please try again later,if you have tried for a long time, there may be something wrong with the server, please contact PE to solve the problem")
else
    local r, v = pcall(restful_admin)
    if not r then 
        if gray_admin_status.backupped and not gray_admin_status.restore_status and
           gray_admin_status.phase < BEFORE_RELOAD_NEW_CONF then
            restore_original_conf(global_sid, global_success_files)
        end
        if arg_dowhat ~= 'look' then unlock() end
        return print_and_log(ecode_env_exception, "fatal error!! there may be some bugs in server, PLEASE CONTACT PE TO CHECK IF THE NGINX SERVER WAS CRASHED!!! Due to:"..v)  
    end
    if arg_dowhat ~= 'look' then unlock() end
end

